package com.liyunc.demo.bundle.file.manager.service;

import com.liyunc.demo.bundle.file.manager.model.dto.input.AssetFileContentUpdateDto;
import com.liyunc.demo.bundle.file.manager.utils.converter.AssetFileConverter;
import com.liyunc.demo.bundle.file.manager.model.dto.output.AssetFileDto;
import com.liyunc.demo.bundle.file.manager.model.entity.AssetFile;
import com.liyunc.demo.bundle.file.manager.dao.repository.AssetFileEntityRepository;
import com.liyunc.demo.bundle.file.manager.model.dto.input.AssetFileContentRequestDto;
import com.liyunc.demo.bundle.file.manager.model.dto.input.AssetFileRequestDto;
import com.liyunc.demo.comp.common.db.TransactionHook;
import com.liyunc.demo.comp.common.utils.FilesUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.Optional;

@Service
@Slf4j
public class AssetFileService extends BaseService {

    @Autowired
    private AssetFileConverter mapper;
    @Autowired
    private AssetFileEntityRepository fileEntityRepository;
    @Autowired
    private TransactionHook transactionHook;


    public Optional<AssetFileDto> upload(AssetFileRequestDto requestDto) {
        if (Objects.isNull(requestDto)) {
            return Optional.empty();
        }
        final MultipartFile file = requestDto.getFile();
        final String filename = file.getOriginalFilename();
        // 检查文件大小
        if (!checkSize(file)) {
            return Optional.empty();
        }
        Path relativePath = getRelativePath(filename);
        Path absolutePath = getAbsolutePath(relativePath);
        if (FilesUtils.checkPath(absolutePath, false)) {
            return Optional.empty();
        }
        try {
            if (file instanceof CommonsMultipartFile commonsMultipartFile
                && commonsMultipartFile.getFileItem() instanceof DiskFileItem diskFileItem) {
                if (diskFileItem.isInMemory()) {
                    //如果文件太小在内存里，直接用io流保存文件
                    Files.copy(file.getInputStream(), absolutePath);
                }
                // 将上传文件从临时目录移动到目标位置
                Files.move(diskFileItem.getStoreLocation().toPath(), absolutePath);
            }
        } catch (IOException exception) {
            log.error("Failed to save file {}", relativePath, exception);
            return Optional.empty();
        }
        //保存文件信息到数据库
        final String checksum = FilesUtils.getChecksum(absolutePath);
        AssetFile entity = mapper.fromUpload(
            filename,
            relativePath.toString(),
            file.getContentType(),
            requestDto.getOwnerType(),
            requestDto.getOwnerId(),
            checksum
        );
        return Optional.of(
            mapper.forEntity(fileEntityRepository.saveAndFlush(entity)));
    }

    public Optional<AssetFileDto> upload(AssetFileContentRequestDto requestDto) {
        if (Objects.isNull(requestDto)) {
            return Optional.empty();
        }
        final String filename = requestDto.getFilename();
        final String mimeType = FilesUtils.getMimeType(filename);
        final String content = requestDto.getContent();
        // 检查文件大小
        if (!checkSize(content)) {
            return Optional.empty();
        }
        Path relativePath = getRelativePath(filename);
        Path absolutePath = getAbsolutePath(relativePath);
        if (FilesUtils.checkPath(absolutePath, false)) {
            return Optional.empty();
        }
        try (FileWriter writer = new FileWriter(absolutePath.toFile(),
            StandardCharsets.UTF_8, true)) {
            writer.append(content);
            writer.flush();
            //保存文件信息到数据库
            final String checksum = FilesUtils.getChecksum(absolutePath);
            AssetFile entity = mapper.fromUpload(
                filename,
                relativePath.toString(),
                mimeType,
                requestDto.getOwnerType(),
                requestDto.getOwnerId(),
                checksum
            );
            return Optional.of(
                mapper.forEntity(fileEntityRepository.saveAndFlush(entity)));
        } catch (IOException exception) {
            log.info("Failed to save file content to {}", relativePath, exception);
            return Optional.empty();
        }
    }

    public Optional<AssetFileDto> update(Long scriptId, AssetFileContentUpdateDto updateDto) {
        if (Objects.isNull(updateDto)) {
            return Optional.empty();
        }
        final String content = updateDto.getContent();
        final Integer version = updateDto.getVersion();
        AssetFile oldEntity = fileEntityRepository.findById(scriptId).orElse(null);
        if (Objects.isNull(oldEntity)) {
            return Optional.empty();
        }
        String newFilename = getFilename();
        Path absolutePath = getAbsolutePath(Paths.get(oldEntity.getFilepath()));
        Path tempAbsolutePath = Paths.get(
            FilesUtils.getParent(absolutePath).toString(), newFilename);
        Path tempRelativePath = Paths.get(
            FilesUtils.getParent(oldEntity.getFilepath()).toString(), newFilename);
        AssetFile result;
        //先存储到临时文件
        try (FileWriter writer = new FileWriter(tempAbsolutePath.toFile(),
            StandardCharsets.UTF_8, false)) {
            writer.append(content);
            writer.flush();
            oldEntity.setChecksum(FilesUtils.getChecksum(tempAbsolutePath));
        } catch (IOException exception) {
            log.error("Failed to save tempAbsolutePath file {}", tempAbsolutePath, exception);
            return Optional.empty();
        }
        if (Objects.isNull(version)) {
            oldEntity.setFilepath(tempRelativePath.toString());
            result = fileEntityRepository.saveAndFlush(oldEntity);
        } else {
            try {
                if (oldEntity.getVersion() >= version) {
                    log.error("Failed to update file,version error {}",
                        oldEntity.getVersion());
                    Files.delete(tempAbsolutePath);
                    return Optional.empty();
                }
                int saved = fileEntityRepository.saveAndFlushByVersion(
                    tempRelativePath.toString(), version,
                    oldEntity.getId(), oldEntity.getVersion()
                );
                if (saved != 1) {
                    log.error("Failed to update file,save info error {}",
                        oldEntity.getVersion());
                    Files.delete(tempAbsolutePath);
                    return Optional.empty();
                }
            } catch (IOException exception) {
                log.error("Failed to delete tempAbsolutePath file {}", tempAbsolutePath, exception);
            }
            result = fileEntityRepository.findById(oldEntity.getId()).orElse(null);
        }
        // 如果result不为空，说明保存成功，需要删除原文件
        if (Objects.nonNull(result) && Files.exists(absolutePath)) {
            transactionHook.afterTransaction(() -> {
                try {
                    //删除原文件
                    Files.delete(absolutePath);
                } catch (IOException exception) {
                    log.error("Failed to delete old file {}", absolutePath, exception);
                }
            });
        }
        return Optional.of(mapper.forEntity(result));
    }


    public Optional<AssetFileDto> delete(Long fileId) {
        Optional<AssetFile> result = fileEntityRepository.findById(fileId);
        result.ifPresent(item -> fileEntityRepository.delete(item));
        return result.map(mapper::forEntity);
    }


    public Optional<Pair<File, AssetFileDto>> download(Long fileId) {
        Optional<AssetFile> result = fileEntityRepository.findById(fileId);
        if (result.isPresent()) {
            Path path = getAbsolutePath(Paths.get(result.get().getFilepath()));
            if (Files.exists(path)) {
                return Optional.of(
                    new MutablePair<>(path.toFile(), mapper.forEntity(result.get())));
            } else {
                return Optional.empty();
            }
        }
        return Optional.empty();
    }
}
